package com.basics.common.utils.oss;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.model.*;
import com.basics.common.exception.CustomException;
import com.basics.common.utils.DateUtils;
import com.basics.common.utils.redis.RedisUtil;
import com.basics.common.utils.video.CoverVo;
import com.basics.common.utils.video.VideoUtils;
import com.beust.jcommander.internal.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

@Slf4j
@Component
public class AliOSSUtil {
    private static String accessKeyId = "LTAI5tS9DuDwAR9S49XNc82M";
    private static String accessKeySecret = "46cLiumPTOaoZtj1eXqnEzQan5922v";
    private static String endpoint = "oss-cn-hangzhou-internal.aliyuncs.com";
    private static String bucketName = "dimocrm";
    private static String ossDomain = "oss.dimoclub.com/";

    private static volatile OSSClient ossClient = null;

    @Autowired
    protected static RedisUtil redisUtil;

    @Autowired
    public void setRedisUtil(RedisUtil redisUtil) {
        AliOSSUtil.redisUtil = redisUtil;
    }

    public AliOSSUtil() {
    }

    private static OSSClient initOSS() {
        if (ossClient == null) {
            synchronized (OSSClient.class) {
                if (ossClient == null) {
                    ossClient = new OSSClient(endpoint,
                            new DefaultCredentialProvider(accessKeyId, accessKeySecret),
                            new ClientConfiguration());
                }
            }
        }
        return ossClient;
    }

    private static String getURLHead() {
        return ossDomain;
    }

    private static String getObjectName(String fileURL) {
        return fileURL.substring(getURLHead().length());
    }

    private static String getRealName(String oranName) {
        return getURLHead() + oranName;
    }

    private static void printUploadSuccessInfo(String fileURL) {
        System.out.println("upload success， path = " + getRealName(fileURL));
    }

    private static void printDeleteSuccessInfo(String fileURL) {
        System.out.println("delete success， path = " + getRealName(fileURL));
    }

    private static String getRandomImageName(String oranName) {
        String uuid = UUID.randomUUID().toString().replace("-", "");
        int cutPoint = oranName.lastIndexOf("/") + 1;
        if (cutPoint != 0) {
            String head = oranName.indexOf("/") == 0 ? oranName.substring(1, cutPoint) : oranName.substring(0, cutPoint);
            String tail = oranName.substring(cutPoint);
            return head + uuid + tail;
        } else {
            return uuid + oranName;
        }
    }

    private static String getFileName(String oranName) {
        int cutPoint = oranName.lastIndexOf("/") + 1;
        if (cutPoint != 0) {
            String head = oranName.indexOf("/") == 0 ? oranName.substring(1, cutPoint) : oranName.substring(0, cutPoint);
            String tail = oranName.substring(cutPoint);
            return head + tail;
        } else {
            return oranName;
        }
    }

    public static String createBucket(String bucket) {
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);
        ossClient.createBucket(bucket);
        ossClient.shutdown();
        return bucket;
    }

    public static String getBucketName(String fileURL) {
        String prefix = "http://";
        String suffix = ".";
        int beginIndex = fileURL.indexOf(prefix);
        int endIndex = fileURL.indexOf(suffix);
        if (beginIndex == -1) {
            prefix = "https://";
            beginIndex = fileURL.indexOf(prefix);
            if (beginIndex == -1) {
                return null;
            }
        }

        beginIndex = prefix.length();
        return fileURL.substring(beginIndex, endIndex);
    }

    public static void useBucketName(String bucket) {
        bucketName = bucket;
    }

    public static String upLoadTextFile(String oranFileName, String content) {
        String objectName = getRandomImageName(oranFileName);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);
        ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));
        printUploadSuccessInfo(objectName);
        ossClient.shutdown();
        return getRealName(objectName);
    }

    public static String uploadBytesFile(String oranFileName, byte[] content) {
        String objectName = getRandomImageName(oranFileName);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);
        ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content));
        printUploadSuccessInfo(objectName);
        ossClient.shutdown();
        return getRealName(objectName);
    }

    public static String uploadNetworkFlows(String oranFileName, String url) {
        String objectName = getRandomImageName(oranFileName);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);

        try {
            InputStream inputStream = (new URL(url)).openStream();
            Throwable var5 = null;

            try {
                ossClient.putObject(bucketName, objectName, inputStream);
            } catch (Throwable var15) {
                var5 = var15;
                throw var15;
            } finally {
                if (inputStream != null) {
                    if (var5 != null) {
                        try {
                            inputStream.close();
                        } catch (Throwable var14) {
                            var5.addSuppressed(var14);
                        }
                    } else {
                        inputStream.close();
                    }
                }

            }
        } catch (Exception var17) {
            var17.printStackTrace();
        }

        printUploadSuccessInfo(objectName);
        ossClient.shutdown();
        return getRealName(objectName);
    }

    public static String uploadFile(String oranFileName, File file) {
        String objectName = getRandomImageName(oranFileName);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);

        try {
            InputStream inputStream = new FileInputStream(file);
            Throwable var5 = null;

            try {
                ossClient.putObject(bucketName, objectName, inputStream);
            } catch (Throwable var15) {
                var5 = var15;
                throw var15;
            } finally {
                if (inputStream != null) {
                    if (var5 != null) {
                        try {
                            inputStream.close();
                        } catch (Throwable var14) {
                            var5.addSuppressed(var14);
                        }
                    } else {
                        inputStream.close();
                    }
                }

            }
        } catch (Exception var17) {
            var17.printStackTrace();
        }

        printUploadSuccessInfo(objectName);
        ossClient.shutdown();
        return getRealName(objectName);
    }

    public static String uploadOriginalFile(String oranFileName, File file) {
        String objectName = getFileName(oranFileName);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);

        try {
            InputStream inputStream = new FileInputStream(file);
            Throwable var5 = null;

            try {
                ossClient.putObject(bucketName, objectName, inputStream);
            } catch (Throwable var15) {
                var5 = var15;
                throw var15;
            } finally {
                if (inputStream != null) {
                    if (var5 != null) {
                        try {
                            inputStream.close();
                        } catch (Throwable var14) {
                            var5.addSuppressed(var14);
                        }
                    } else {
                        inputStream.close();
                    }
                }

            }
        } catch (Exception var17) {
            var17.printStackTrace();
        }

        printUploadSuccessInfo(objectName);
        ossClient.shutdown();
        return getRealName(objectName);
    }

    public static String uploadInputSteam(String oranFileName, InputStream inputStream) {
        String objectName = getRandomImageName(oranFileName);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);
        try {
            ossClient.putObject(bucketName, objectName, inputStream);
        } catch (Exception var5) {
            var5.printStackTrace();
        }

        printUploadSuccessInfo(objectName);
        ossClient.shutdown();
        return getRealName(objectName);
    }

    public static String uploadLocalFile(String oranFileName, String localFileName) {
        String objectName = getRandomImageName(oranFileName);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(localFileName));
        ossClient.putObject(putObjectRequest);
        printUploadSuccessInfo(objectName);
        ossClient.shutdown();
        return getRealName(objectName);
    }

    public static void deleteFile(String fileURL) {
        String objectName = getObjectName(fileURL);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);
        ossClient.deleteObject(bucketName, objectName);
        printDeleteSuccessInfo(fileURL);
        ossClient.shutdown();
    }

    public static boolean exists(String fileURL) {
        String objectName = getObjectName(fileURL);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);
        boolean found = ossClient.doesObjectExist(bucketName, objectName);
        ossClient.shutdown();
        return found;
    }

    public static void downloadFileToLoacal(String fileURL, String localFileName) {
        String objectName = getObjectName(fileURL);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);
        ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File(localFileName));
        ossClient.shutdown();
    }

    public static StringBuffer downloadStream(String fileURL) {
        String objectName = getObjectName(fileURL);
        OSS ossClient = (new OSSClientBuilder()).build(endpoint, accessKeyId, accessKeySecret);
        OSSObject ossObject = ossClient.getObject(bucketName, objectName);
        StringBuffer sb = new StringBuffer();

        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(ossObject.getObjectContent()));
            Throwable var6 = null;

            try {
                while(true) {
                    String line = reader.readLine();
                    if (line == null) {
                        break;
                    }

                    sb.append(line);
                    System.out.println("\n" + line);
                }
            } catch (Throwable var16) {
                var6 = var16;
                throw var16;
            } finally {
                if (reader != null) {
                    if (var6 != null) {
                        try {
                            reader.close();
                        } catch (Throwable var15) {
                            var6.addSuppressed(var15);
                        }
                    } else {
                        reader.close();
                    }
                }

            }
        } catch (Exception var18) {
            var18.printStackTrace();
        }

        ossClient.shutdown();
        return sb;
    }

    public static InputStream getOssFile(String objectName){
        InputStream inputStream = null;
        try{
            ossClient =  initOSS();
            OSSObject ossObject = ossClient.getObject(bucketName,objectName);
            inputStream = new BufferedInputStream(ossObject.getObjectContent());
        }catch (Exception e){
            log.info("文件获取失败" + e.getMessage());
        }
        return inputStream;
    }


    /**
     * 分片上传
     *
     * @param req 上传参数
     * @return
     */
    public static UpdateChunkFileData uploadChunk(UploadChunkFileReq req,MultipartFile file) {
        UploadChunkFileParam param = new UploadChunkFileParam();
        BeanUtils.copyProperties(req, param);
        if(StringUtils.isEmpty(req.getDir())){
            req.setDir("dm-common");
        }
        if (ObjectUtil.isEmpty(param.getKey())) {
            String key = getKey(req.getDir(), param.getIdentifier(), param.getFilename());
            param.setKey(key);
        }
        return  uploadChunk(param.getUploadId(), param.getKey(),file, param.getChunkNumber(),
                param.getCurrentChunkSize(), param.getTotalChunks());
    }

    /**
     * 分片上传
     * 1、检查文件是否上传
     * 2、检查文件是否第一次上传，第一次上传创建上传id uploadId
     * 3、检查是否是断点续传，如果是返回已上传的分片
     * 4、分片上传到阿里云OSS上，并记录上传信息到Redis
     * 5、判断是否已上传完成，已完成：合并所有分片为源文件
     *
     * @param uploadId   上传id
     * @param key        文件在OSS上的key
     * @param file       文件分片
     * @param chunkIndex 分片索引
     * @param chunkSize  分片大小
     * @param chunkCount 总分片数
     * @return
     */
    public static UpdateChunkFileData uploadChunk(String uploadId, String key, MultipartFile file, Integer chunkIndex,
                                  long chunkSize, Integer chunkCount) {
        if (ObjectUtil.isEmpty(key)) {
            key = getKey("dm-common", null, FileUtil.getSuffix(file.getOriginalFilename()));
        }
        ossClient = initOSS();
        try {
            log.error("【分片上传key】-key》{}",key);
            UpdateChunkFileData data = new UpdateChunkFileData();
            // 判断是否上传
            if (checkExist(key)) {
                log.error("【第一次分片上传完成】-key》{}",key);
                data.setSkipUpload(true);
                data.setUrl(getUrl(key));
                CoverVo vo = getCoverImg(getOssFile(key), key.substring(0, key.lastIndexOf(".")));
                data.setCoverImg(vo.getCoverImg());
                data.setWidth(vo.getWidth());
                data.setHeight(vo.getHeight());
                log.error("【第一次分片上传完成】-URl》{}",data);
                return data;
            }
            // 判断是否第一次上传
            if (StringUtils.isBlank(uploadId)) {
                uploadId = uploadChunkInit(file, key);
                if(chunkCount>1){
                    PartETag partETag = uploadChunkPart(uploadId, key, file.getInputStream(), chunkIndex, chunkSize, chunkCount);
                    // 分片上传完成缓存key
                    redisUtil.hset(OssContant.REDIS_ALI_OSS_KEY + uploadId, chunkIndex + ",", JSONUtil.toJsonStr(partETag));
                    data.setSkipUpload(false);
                    data.setUploaded(null);
                    data.setUploadId(uploadId);
                    return data;
                }

            }
            // 检查分片是否已上传 实现断点续传
            if (file == null) {
                Map<Object, Object> uploadedCache = redisUtil.hmget(OssContant.REDIS_ALI_OSS_KEY + uploadId);
                List<Integer> uploaded = Lists.newArrayList();
                for (Map.Entry<Object, Object> entry : uploadedCache.entrySet()) {
                    uploaded.add(JSONUtil.toBean(entry.getValue().toString(), PartETag.class).getPartNumber());
                }
                data.setSkipUpload(false);
                data.setUploadId(uploadId);
                data.setUploaded(uploaded);
                return data;
            }
            // 上传分片
            PartETag partETag = uploadChunkPart(uploadId, key, file.getInputStream(), chunkIndex, chunkSize, chunkCount);
            // 分片上传完成缓存key
            redisUtil.hset(OssContant.REDIS_ALI_OSS_KEY + uploadId, chunkIndex + ",", JSONUtil.toJsonStr(partETag));
            // 取出所有已上传的分片信息
            Map<Object, Object> dataMap = redisUtil.hmget(OssContant.REDIS_ALI_OSS_KEY + uploadId);
            List<PartETag> partETagList = Lists.newArrayList();
            for (Map.Entry<Object, Object> entry : dataMap.entrySet()) {
                partETagList.add(JSONUtil.toBean(entry.getValue().toString(), PartETag.class));
            }
            // 判断是否上传完成
            if (dataMap.keySet().size() == chunkCount) {
                 uploadChunkComplete(uploadId, key, partETagList);
                for (Object mapKey : dataMap.keySet()) {
                    redisUtil.hdel(OssContant.REDIS_ALI_OSS_KEY + uploadId, mapKey);
                }

                data.setSkipUpload(true);
                data.setUploadId(uploadId);
                data.setUrl(getUrl(key));
                log.error("【分片上传完成】-key》{}",key);
                CoverVo vo = getCoverImg(getOssFile(key), key.substring(0, key.lastIndexOf(".")));
                data.setCoverImg(vo.getCoverImg());
                data.setWidth(vo.getWidth());
                data.setHeight(vo.getHeight());
                log.error("【分片上传完成】-URl》{}",data);
            } else {
                List<Integer> list = partETagList.stream().map(PartETag::getPartNumber).collect(Collectors.toList());
                data.setSkipUpload(false);
                data.setUploadId(uploadId);
                data.setUploaded(list);
            }
            return data;
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException("上传失败：" + e.getMessage());
        }

    }



    /**
     * 初始化分片上传
     *
     * @param key
     * @return 分片上传的uploadId
     */
    public static String uploadChunkInit(MultipartFile file, String key) {
        if (ObjectUtil.isEmpty(key)) {
            key = getKey("dm-common", null, FileUtil.getSuffix(file.getOriginalFilename()));
        }
        return uploadChunkInit(key);
    }

    /**
     * 初始化上传id uploadId
     *
     * @param key
     * @return
     */
    public static String uploadChunkInit(String key) {
        if (ObjectUtil.isEmpty(key)) {
            throw new CustomException("key不能为空");
        }
        ossClient = initOSS();
        try {
            // 创建分片上传对象
            InitiateMultipartUploadRequest uploadRequest = new InitiateMultipartUploadRequest(bucketName, key);
            // 初始化分片
            InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(uploadRequest);
            // 返回uploadId，它是分片上传事件的唯一标识，您可以根据这个uploadId发起相关的操作，如取消分片上传、查询分片上传等。
            return result.getUploadId();
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException("初始化分片失败：" + e.getMessage());
        }
    }

    /**
     * 上传分片文件
     *
     * @param uploadId   上传id
     * @param key        key
     * @param instream   文件分片流
     * @param chunkIndex 分片索引
     * @param chunkSize  分片大小
     * @return
     */
    public static PartETag uploadChunkPart(String uploadId, String key, InputStream instream,
                                           Integer chunkIndex, long chunkSize, Integer chunkCount) {
        ossClient = initOSS();
        try {
            UploadPartRequest partRequest = new UploadPartRequest();
            // 阿里云 oss 文件根目录
            partRequest.setBucketName(bucketName);
            // 文件key
            partRequest.setKey(key);
            // 分片上传uploadId
            partRequest.setUploadId(uploadId);
            // 分片文件
            partRequest.setInputStream(instream);
            // 分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
            partRequest.setPartSize(chunkSize);
            // 分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
            partRequest.setPartNumber(chunkIndex);
            // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
            UploadPartResult uploadPartResult = ossClient.uploadPart(partRequest);
            // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在redis中。
            return uploadPartResult.getPartETag();
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException("分片上传失败：" + e.getMessage());
        }
    }

    /**
     * 文件合并
     *
     * @param uploadId  上传id
     * @param key       key
     * @param chunkTags 分片上传信息
     * @return
     */
    public static CompleteMultipartUploadResult uploadChunkComplete(String uploadId, String key, List<PartETag> chunkTags) {
        ossClient = initOSS();
        try {
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, key, uploadId, chunkTags);
            CompleteMultipartUploadResult result = ossClient.completeMultipartUpload(completeMultipartUploadRequest);

            log.error("【合成分片结果】-》{}",result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException("分片合并失败：" + e.getMessage());
        }
    }

    /**
     * 获取上传文件的key
     * 上传和删除时除了需要bucketName外还需要此值
     *
     * @param prefix   前缀（非必传），可以用于区分是哪个模块或子项目上传的文件,默认 file 文件夹
     * @param fileName 文件名称（非必传），如果为空默认生成文件名，格式：yyyyMMdd-UUID
     * @param suffix   后缀 , 可以是 png jpg
     * @return
     */
    private static String getKey(final String prefix, final String fileName, final String suffix) {
        StringBuffer keySb = new StringBuffer();
        // 前缀处理
        if (StringUtils.isNotEmpty(prefix)) {
            keySb.append(prefix);
        } else {
            keySb.append("dm-common");
        }
        keySb.append("/");
        // 文件名处理
        if (StringUtils.isBlank(fileName)) {
            // 上传时间 因为后期可能会用 - 将key进行split，然后进行分类统计
            keySb.append(DateUtils.parseDateToStr("yyyyMMdd",new Date()));
            keySb.append("-");
            // 生成uuid
            keySb.append(IdUtil.randomUUID());
        } else {
            keySb.append(fileName);
        }
        // 后缀处理
        if (StringUtils.isBlank(suffix)) {
            throw new NullPointerException("文件后缀不能为空");
        }
        if (suffix.contains(".")) {
            keySb.append(suffix.substring(suffix.lastIndexOf(".")));
        } else {
            keySb.append("." + suffix);
        }
        return keySb.toString();
    }

    public static Boolean checkExist(String key) {
        ossClient = initOSS();
        return ossClient.doesObjectExist(bucketName, key);
    }

    /**
     * 根据key生成文件的访问地址
     *
     * @param key
     * @return
     */
    public static String getUrl(String key) {
        // 拼接文件访问路径。由于拼接的字符串大多为String对象，而不是""的形式，所以直接用+拼接的方式没有优势
        StringBuffer url = new StringBuffer();
        url.append("https://")
                .append(ossDomain)
                .append(key);
        return url.toString();
    }

    public static String getCoverUrl(String key) {
        // 拼接文件访问路径。由于拼接的字符串大多为String对象，而不是""的形式，所以直接用+拼接的方式没有优势
        StringBuffer url = new StringBuffer();
        url.append("https://")
                .append(ossDomain)
                .append("videoCoverImg/")
                .append(key).append(".png");
        return url.toString();
    }

    private static CoverVo getCoverImg(InputStream inputStream, String key)throws Exception{
        return VideoUtils.fetchPic(inputStream, "png",key);
    }

}
